Repository navigation
Conversation
|
@Stener1 is attempting to deploy a commit to the seanbrydonio's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Could a maintainer add |
There was a problem hiding this comment.
All reported issues were addressed across 41 files
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
302c509 to
2575aac
Compare
|
Rebased onto current
Codex's review of the fixes found two more (a cancel lost across a relaunch, and an archive failure arriving before its 202); both are fixed in the last commit. The PR body has the new evidence, including |
2575aac to
38ab82b
Compare
|
Rebased the stack (#175,
Re-run on the new head: |
There was a problem hiding this comment.
All reported issues were addressed across 15 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 8 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
…199) Archive… still asks, but pressing Archive closes the dialog at once. The worktree shows Archiving… and goes when its box gets to it, through the same per-box queue Remove worktree uses, now carrying the kind. The toasts are today's: Archived (branch kept), Archiving (its script is running), and a warning for uncommitted work. Bulk archive in the Worktrees table skips a worktree already on its way out, so its box never gets a second request for it. The mock takes ?slowremove=MS to make a box slow to remove a worktree, and the verify skill gains a worktree-archive scenario and feature file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Confirming Stop N agents? (and Clean up…) now closes the dialog at once.
The stopping, and the archiving when it was ticked, goes on in a tray in
the bottom-right corner, above the status bar and the loops panel: one
row per agent, grouped by box, with progress in the title ("Stopping 17
agents · 9/17"). It folds to one line (remembered), Stop the rest sends
nothing more, and a clean run sums itself up ("17 stopped, 15 archived"),
shows the toast it always did and fades. If something was kept or failed
it stays as a report of those rows until you dismiss it. Jobs live in an
app-level store, so they carry on when you leave the board.
The sidebar's Archive… shows up in the tray too, as a one-row job.
Concurrency and the archive safety rules are unchanged; bulk archives now
wait in the same per-box line as every other removal. The mock gains
?slowstop=MS to make stops slow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dashboard-stop-archive scenario now shows the dialog gone at once, the tray mid-run, folded, while another view is open, its report, a clean run fading and Stop the rest. worktree-archive checks the one-row job. The feature files say how the tray behaves, and the changelog moves the archive bullet back under Unreleased. The tray lifts above a page's floating bar so it never hides the board's selection buttons, a long summary wraps, and a one-row job drops its count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dashboard-stop-live makes its own worktrees and agents on dev-box.sh's box, stops three with archiving ticked, and checks the tray's report against git worktree list and git branch there; then it archives a fourth from the sidebar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
keepsWork and the archive outcome messages live in lib/removing.ts, so the stop job, the single archive and the Worktrees table share them, and the stop job no longer takes keepsWork as a dep. Every archive through queueRemoval is a tray job, so the tray flag goes. plural moves to lib/format.ts. The tray uses Button for Fold and Dismiss, one icon per outcome and per row state, and memoised cards and rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…be held back From the review: an archive waiting its turn on a box now reads the box's agents again when its turn comes, so one started there meanwhile keeps the worktree. Each worktree is marked as it is queued, so Archive… from the sidebar can't ask for it a second time, and one already on its way out is skipped. Rows waiting to archive count as unsent, so Stop the rest stays offered until they go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ds it back Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… halt it The board's bulk Stop (and archiving where it's safe), the Worktrees table's Stop sessions and Archive, and the sidebar's Stop everything are now one stop run per box with runs: a new run template with two leaves, stop_session and archive_worktree, that keep the app's safety rules (never forced, branch kept, dirty / another agent / a failed stop keep it, before:-gates honoured) and wait for the archive script. The app follows each run; a row fails only when the box says so, and the job comes back to the tray after a relaunch. dropWorktree joins a removal already in flight rather than running the archive script twice. Boxes without runs keep the app's own loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and keeps a worktree an agent started in meanwhile Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it, and a lost one fails its rows in the table Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent starts unseen as one goes A stop run's archive step waits its turn on the box (archiveTurn) before it looks the worktree up, so archive scripts from separate runs never run side by side, and one archived meanwhile is found gone. The last look for agents and the removal hold a lock that session starts take for reading (removeAfter, startUnseen): an agent asked for in between waits, then finds its folder gone. A session listing that fails now fails the step instead of passing for "another agent". A worktree is checked for uncommitted work before it counts as on its way out, so a second ask for a dirty one is refused rather than answered 202. The dedup test waits for worktree.archive.started and quotes its temp path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ive… is the box's to finish A bulk Stop is kept (berth.jobs.stopRuns) before its start is sent, with each box's params. A box that doesn't answer is asked again under the same idempotency key until it does, its rows "Waiting for BOX", instead of failing them after three tries; after a relaunch an unanswered share is asked for again. Stop the rest meanwhile looks the run up by its group (GET runs?group=): one the box took is followed and cancelled, and with none the rows read Not stopped. A run the box no longer has lets go of its worktrees' Archiving… marks. Archive… from the sidebar, zen and the launcher is a one-worktree stop run on a box with runs: its sessions stop, it waits the box's own turn, it finishes while the laptop sleeps, and the tray's row ends only when the worktree is gone or kept. Elsewhere the app's line for the box (and the board's own loop) holds until the box says the archive script finished (archiveEnded), so scripts on one box never overlap and a row no longer reads done while its script runs. The mock lists runs by group, answers a repeated key with the run it has, ends a stop run's maps with it, and fails an archive step whose session listing fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al, the tray hidden takes no room runPool walks its queue by length, so an undefined item runs too. team.ts drops its copy of plural for format.ts's. The jobs tray, hidden under Settings, sets --berth-jobs-h to 0 instead of its gap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the runs guide and the verify skill The live scenario no longer expects the 400 a bulk Stop never sends: it reads the box's stop runs back instead, for the bulk Stop and the sidebar's Archive…. The feature map says how a start that goes unanswered is handled, that jobs are kept 24 hours at most, that a run joining an archive reads Archived, that a bulk Stop still stops agents whose worktree is kept, the review/ branch exception, and the real queueRemoval call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he archive scenarios expect the box to finish The mock plays a box's archive line across its stop runs, as archiveTurn does. worktree-archive expects Archive… on devl and gpu to be a stop run there: judge-v2 waits in gpu's line, qa-deck's row stays Archiving… until its script is done, and one toast says Archived. Toasts are found by role, so a second one for the same archive fails. dashboard-stop-archive's report reads 2 archived, since the app's loop now waits for the script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a real box dashboard-stop-unanswered kills the dev box's berthd as Stop is pressed and starts it again 20 s later: no row fails meanwhile, and the box ends with one stop run for the job, followed to its end, with git agreeing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…at fails fast isn't read as archived From Codex's review. Stop the rest pressed while a box answers nothing is kept with the job (onCancel), so after a relaunch the job comes back cancelled: a share whose start went unanswered is only looked up by its group, its run cancelled if the box took it, and never sent again. removeWorktreeOnBox notes how an archive ends from before it sends it, so a worktree.archive.failed that beats the 202 still fails the tray's row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… be finished git worktree remove ran under the 60 s limit every git call has. A big worktree on a busy box took longer, git was killed mid-delete and said nothing, so the row read "Couldn't archive: git worktree remove:" and the folder was left without its .git, which git then refused to remove again. - git worktree remove gets 10 minutes (BERTH_WORKTREE_REMOVE_TIMEOUT sets another), and a failure with no output says how it ended: timed out, stopped by a signal, cancelled, or its exit status. - A worktree git lists as prunable is finished on archive: what's left of its folder, then git's record of that one worktree (never git worktree prune, which would drop every half-removed one's record), branch kept. Only when git, asked through the record's HEAD and index, sees nothing but files the removal already deleted, and no repository is inside. One whose folder is gone is pruned the same way. Its archive script isn't run again. - A removal's last look for agents holds only its own folder, so a removal git takes minutes over doesn't hold up session starts elsewhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dashboard-stop-interrupted-live restarts the dev box with a git that deletes one worktree's .git and then hangs, and a 5 s removal budget. A bulk Stop's row must say it timed out, and Archive... must then finish the half-removed worktree, keeping its branch. VERIFY_BERTHD runs it on an older build for a before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whether a worktree still has its .git is looked at each time the list is read, rather than taken from git's "prunable" line, so a listing read before the .git went (a cached one) doesn't show it as live and send its archive back to git worktree remove, which refuses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…still be finished A removal stopped halfway can have deleted .gitignore files before what they ignore, so node_modules read as new files and every such worktree was kept as having uncommitted changes. The deleted .gitignore files are put back as committed, from the worktree's own index, and git is asked again, so ignore rules apply as git worktree remove would see them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… real one did Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it afterwards Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n, and one already gone says so finishRemoval's refusals (a .git git can't read, a worktree or repository inside, no record, unsaved work) are now asked before dropWorktreeIf stops any service, as its dirty check is. A stop run that finishes a worktree whose folder is gone releases its ports and publishes worktree.removed, since the watcher can't have said so if berthd was down when it went. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018aGVrwjScLWvXHoDh7e7yw
…, and docs that say when a half-removed worktree is kept Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018aGVrwjScLWvXHoDh7e7yw
…eview too The mark is read before RemoveWorktree forgets it; then its ref goes and review.removed is published, as an ordinary removal does. removeAfter's comment now says the hold lasts until the removal ends, as lookGuard's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018aGVrwjScLWvXHoDh7e7yw
ec3307c to
1fbd0a0
Compare
Summary
Closing the laptop mid bulk Stop used to halt it. The app ran the whole sequence, so in-flight and queued requests failed on wake, and agents ended up stopped but not archived. Now each box with runs does its share itself, as one durable
stoprun, and so does the sidebar's single Archive…:stoptemplate andstop_session/archive_worktreeleaves (runstop.go).dropWorktreegets an in-flight guard, so a second ask (a resumed run, a duplicate run, a second DELETE) joins the first instead of running the archive script again. The dirty check comes before that guard, so a second ask for a dirty worktree is refused, not answered 202.archiveTurnserializes archives across a box's stop runs.removeAfter/startUnseenclose the check-then-remove race: no session starts between the last look for agents and the folder going. If berthd stops mid-archive, the step stays unfinished in the journal for the resumed run.lib/stop-job.tskeeps the job before it sends, asks an unanswered box again under one key until it answers, and looks a run up by its group when Stop the rest comes first. A job comes back to the tray after a relaunch (for at most 24 h), cancelled if it was. The Worktrees table's Stop sessions and Archive, the sidebar's Stop everything in … and its Archive… use the same run (lib/stop-on-box.ts,archiveOnBox).playStop), in each box's archive line.?norunsgives the old path.fix(box): an archive that fails says why…).git worktree removeran under the 60 s limit that every git call has. On a busy box a big worktree took longer: git was killed mid-delete without a word, so the row readCouldn't archive: git worktree remove:, and the folder was left without its.git, which git then refused to remove again (validation failed … .git does not exist). Now:BERTH_WORKTREE_REMOVE_TIMEOUTsets another limit), and a failure with no output says how it ended: timed out, stopped by a signal, cancelled, or its exit status (gitFailed);finishRemoval). What's left of its folder goes, then git's record of that one worktree, nevergit worktree prune, which would drop every half-removed worktree's record and orphan their folders. The branch is kept. It only does this when git, asked through the record's HEAD and index, sees nothing but files the removal already deleted, and no repository is inside the folder. Any.gitignorethe removal deleted is put back as committed first, since otherwisenode_modulesreads as new files; otherwise it'sKept: uncommitted changes, and Remove anyway still forces. A worktree whose folder is gone is pruned the same way. Its archive script isn't run again;.gitis checked each time the list is read, not taken from git'sprunableline, so a cached listing (fix(box): listing projects runs git only when a repository changed #195) can't send such an archive back togit worktree remove;lookGuard, which replaces the box-widelastLookRWMutex), so a removal that takes minutes doesn't hold up session starts anywhere else on the box.stoptemplate inguides/runs.mdx, the new step kinds, changelog bullets, and the verify skill's feature map and scenarios.Evidence
All on the final head, with the verify-berthd skill.
A removal git doesn't finish in time (new
dashboard-stop-interrupted-live, a real dev box, synthetic acme-shop). The box is restarted with a git on its PATH that, forverify-slowonly, deletes the worktree's.gitand.gitignoreand then hangs (with anode_modulesonly that.gitignoreignored), as a big worktree'sgit worktree removeon a loaded box did. Thenverify-slowandverify-okare bulk-stopped with archiving ticked.VERIFY_BERTHD=this branch's previous head): the archive step failed after 1m0.283s, the 60 s git limit, with the error"git worktree remove: ". The tray row ended at the colon.git worktree listshowedverify-slowasprunablewith its files still there, and archiving it again failed for good:git worktree remove: fatal: validation failed, cannot remove working tree: '…/acme-shop-verify-slow/.git' does not exist.BERTH_WORKTREE_REMOVE_TIMEOUT=5s):The scenario allows the console's 404 for
GET sessions/<stopped agent>/screen: the board's card asks for a just-stopped agent's screen once. That happens without this change too, and each failed request is logged as a step.A start the box never answers (new
dashboard-stop-unanswered, a real dev box, synthetic acme-shop). berthd is killed (SIGKILL) as Stop is pressed and started again 20 s later.startStopRungave up after three asks (about 6 s) and failed the rows,Failed: devq is offline, though the box might have the run. That was the old unit testa box that never answers the start fails its rows.Bulk Stop and sidebar Archive… on a real box (
dashboard-stop-live): the report reads 3 stopped, 2 archived, 1 kept; git agrees; the box listsStopping 3 agents: succeededandArchive verify-solo: succeeded. No request is refused any more: the dirty worktree is kept inside the run.The lid closing (
dashboard-stop-lid): the laptop agent was paused for 25 s mid-archive, and on wake the tray read 5 stopped, 3 archived, 2 kept. A reload mid-run brought the job back, ending 2 stopped, 2 archived. The Worktrees table's Stop sessions and Archive each ran as a stop run.One archive at a time per box (mock,
worktree-archive): Archive… on gpu/ci-flake, then judge-v2 at once. Both are stop runs on gpu, and judge-v2 waits in gpu's line (Archived ci-flakeat 5.5 s,Archived judge-v2at 10.0 s). qa-deck's row staysArchiving…until its script is done, then shows one toast.Tests:
TestStopRunsOnABoxArchiveOneAtATime: two runs' scripts logstart end start end.TestNoAgentStartsUnseenAsAWorktreeGoes: a session asked for during the last look waits, then gets no folder.TestAWorktreeAskedAgainIsCheckedForWorkFirst: dirty while the first archive runs, so the second DELETE is refused.worktree.archive.startedand quotes its path.TestAWorktreeRemoveThatFailsWithNoOutputSaysWhyandTestAWorktreeRemoveThatTimesOutSaysSo, with a fake git on PATH;TestAStopRunSaysWhyARemovalFailedthrough a stop run.TestAHalfRemovedWorktreeIsFinished: folder and record gone, branch kept, another half-removed worktree left alone; a live dirty worktree and the main checkout are still refused.TestAHalfRemovedWorktreeWithWorkInItIsKept(a changed or an added file: kept in git's words; Remove anyway deletes it) andTestAHalfRemovedWorktreeHoldingARepositoryIsKept.TestAWorktreeWhoseFolderIsGoneIsPruned(a worktree can be made at that path again) andTestAWorktreeGitStillHasIsNeverDeletedByHand.TestAStopRunFinishesAHalfRemovedWorktree: half-removed →Archived, folder gone →already goneand pruned, one with work in it →Kept: uncommitted changes; the archive script never runs again.TestAHalfRemovedWorktreeThatLostItsGitignore:.gitignorefiles deleted beforenode_modules→ finished; with a really new file as well → kept.TestAListingFromBeforeTheGitWentShowsItDropped: a listing read before the.gitwent still shows the worktree as dropped.TestNoAgentStartsUnseenAsAWorktreeGoesnow also starts a session in another worktree during the last look.kept-reason(a timeout, a signal or a silent failure is a failure, not kept) andstop-job(the box's words reach the row unchanged).go vet ./... && go test -race ./...fails only in tests that fail on main on this machine under load (TestTheCatalogIsCapped,TestReviewRoutesOverTheWire,TestDetach,TestServicesAreCounted);TestReviewRoutesOverTheWirepasses 3/3 alone. On the interrupted-removal head it failed onlyTestDetach(it fails on main's tree too). Under load, earlier runs also failedTestForwardReachesTheBoxAndSurvivesAnAgentRestart,TestATeamSetupResumesAfterBerthdRestartsandTestSleepDurable; each passes 3/3 alone, on this branch and on its previous head.stop-job.test.ts:runPoolwith anundefineditem, andunmarked.pnpm test: 636 pass.dashboard-stop-archive.spec.tsandworktree-remove.spec.tspass, 11/11.typecheck:plugins,check:titles,check:type,check:grid,check:csp,check:themes,tsc -p e2eandpnpm build.cargo checkwas not run: GTK dev libs are missing on this machine, which is unrelated.Merge Danger
Door: two-way
On the box it's additive: a new template, two step kinds, an in-flight guard, an archive line across stop runs, and a lock held only from a removal's last look for agents until the folder goes. Old clients are unaffected, and new clients fall back on boxes without runs. Reverting the PR restores the app's loop.
Blast Radius: bulk-stop and archive
git worktree removemay take up to 10 minutes instead of 60 s, and a worktree git dropped the link to is finished by the box itself (its folder, then its record under.git/worktrees/), only when nothing in it is work. Branches are always kept.🤖 Generated with Claude Code